/*
 * Copyright (c) 2010 Network Security Lab (NSL), Department of Electrical
 *                    Engineering, University of Washington
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation;
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Author: He Wu mdzz@uw.edu
 */

/**
 * @file sensor.c
 * @brief Sensor packet handler.
 */

#include "sensor.h"
#include "router.h"
#include "command_def.h"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>
#include <assert.h>

static __u16 temp[SENSOR_SAMPLE_SIZE];    //!< temperature measurement array
static __u16 status[SENSOR_SAMPLE_SIZE];  //!< status array
static int status_index = 0;              //!< index to status array
static int sensor_pkt_cnt = 0;  //!< number of packets received since last report

pthread_mutex_t packetCounterLock = PTHREAD_MUTEX_INITIALIZER;

/******************************************************************************/

/**
 * @brief Calculates the status to report to base node.
 * @param num_pkts_received Number of sensor packets received since last update.
 * @return Status to report to base node.
 *
 * Not thread safe, to be called only within @see report_status_to_base .
 */
static __u16 det_status_to_report(int num_pkts_received);

/**
 * @brief Calculates the temperature (ADC) reading to report to base node.
 * @param num_pkts_received Number of sensor packets received since last update.
 * @return Temperature (ADC) reading to report to base node.
 *
 * Not thread safe, to be called only within @see report_status_to_base .
 */
static __u16 det_temp_to_report(int num_pkts_received);

/******************************************************************************/

void sensor_pkt_handler(packet *in_pkt)
{
  assert(in_pkt);
  __u16 packet_number;
  __u16 var_x, var_y, var_z;
  __u16 packet_status;
  __u16 present_temp;

  // retrieve sensor data from packet
  packet_number = (__u16)in_pkt->data[0] | ((__u16)in_pkt->data[1] << 8);
  var_x = (__u16)in_pkt->data[2] | ((__u16)in_pkt->data[3] << 8);
  var_y = (__u16)in_pkt->data[4] | ((__u16)in_pkt->data[5] << 8);
  var_z = (__u16)in_pkt->data[6] | ((__u16)in_pkt->data[7] << 8);

  // retrieve status from packet
  packet_status = (__u16)in_pkt->data[8] | ((__u16)in_pkt->data[9] << 8);

  // retrieve temperature (ADC) reading
  present_temp = (__u16)in_pkt->data[10] | ((__u16)in_pkt->data[11] << 8);

  if (sensor_debug) {
    printf("Packet number = %x\n", packet_number);
    printf("Variance X = 0x%x\n", var_x);
    printf("Variance Y = 0x%x\n", var_y);
    printf("Variance Z = 0x%x\n", var_z);
    printf("Status = %d\n", (int)packet_status);
    printf("Temperature = 0x%x\n", present_temp);
  }

  /* critical section */
  pthread_mutex_lock(&packetCounterLock);

  // record temperature
  temp[status_index] = present_temp;
  // record status
  status[status_index] = packet_status;
  // wrap around
  status_index++;
  status_index = (status_index >= SENSOR_SAMPLE_SIZE) ? 0 : status_index;
  // update sensor packet count
  sensor_pkt_cnt++;
  // saturate count
  sensor_pkt_cnt = (sensor_pkt_cnt >= SENSOR_SAMPLE_SIZE) ? SENSOR_SAMPLE_SIZE : sensor_pkt_cnt;

  pthread_mutex_unlock(&packetCounterLock);
  /* critical section */
}


void report_status_to_base()
{
  __u16 report_status = BLUE;
  __u16 report_temp = 0;
  packet out_pkt;
  __u16 localAddr = get_addr();
  initPkt(&out_pkt, HEALTH_DATA_TYPE); // packet initialization

  while (1) {
    /* critical section */
    pthread_mutex_lock(&packetCounterLock);
    if (sensor_pkt_cnt == 0) {
      report_status = BLUE;
      report_temp = 0;
    }
    else {
      report_status = det_status_to_report(sensor_pkt_cnt);
      report_temp = det_temp_to_report(sensor_pkt_cnt);
      // reset
      sensor_pkt_cnt = 0;
      status_index = 0;
    }
    pthread_mutex_unlock(&packetCounterLock);
    /* critical section */

    if (sensor_debug)
      printf("\nReporting status %x, temperature %x to base!\n\n", report_status, report_temp);

    // using format of command packets
    out_pkt.data[0] = 0;
    out_pkt.data[1] = (__u8)HEALTH_DATA;
    out_pkt.data[2] = (__u8)localAddr;
    out_pkt.data[3] = (__u8)(localAddr >> 8);
    out_pkt.data[4] = (__u8)report_status;
    out_pkt.data[5] = (__u8)(report_status >> 8);
    out_pkt.data[6] = (__u8)report_temp;
    out_pkt.data[7] = (__u8)(report_temp >> 8);
    sendtoBase(&out_pkt, 1);

    usleep(SENSOR_STATUS_REPORT_INTERVAL);
  }
}


void health_pkt_handler(packet *in_pkt)
{
  assert(in_pkt);

  __u16 senderAddr = (__u16)in_pkt->data[2] | ((__u16)in_pkt->data[3] << 8);
  __u16 health_status = (__u16)in_pkt->data[4] | ((__u16)in_pkt->data[5] << 8);
  __u16 temp = (__u16)in_pkt->data[6] | ((__u16)in_pkt->data[7] << 8);

  printf("\nReceived health status = 0x%x, temp = 0x%x, from %x\n\n", health_status, temp, senderAddr);
}

/******************************************************************************/

static __u16 det_status_to_report(int num_pkts_received)
{
  assert(num_pkts_received <= SENSOR_SAMPLE_SIZE);

  int green_cnt = 0;
  int red_cnt = 0;
  int i;

  // count status
  for (i = 0; i < num_pkts_received; i++) {
    if (status[i] == GREEN)
      green_cnt++;
    else if (status[i] == RED)
      red_cnt++;
    status[i] = BLUE; // reset array
  }

  // determine status to report by majority vote
  if (green_cnt >= num_pkts_received / 2)
    return GREEN;
  else if (red_cnt >= num_pkts_received / 2)
    return RED;
  else
    return YELLOW;
}


static __u16 det_temp_to_report(int num_pkts_received)
{
  assert(num_pkts_received <= SENSOR_SAMPLE_SIZE);

  int i;
  __u16 sum = 0;

  // get sum
  for (i = 0; i < num_pkts_received; i++) {
    sum += temp[i];
    temp[i] = 0;  // reset array
  }

  // return average or 0 if no sensor packet received
  return (num_pkts_received == 0) ? 0 : sum / num_pkts_received;
}
